package com.yboot.starter.tenant.core.context;

import com.alibaba.ttl.TransmittableThreadLocal;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.StringUtils;

import java.util.ArrayDeque;
import java.util.Deque;

/**
 * 租户上下文管理；参考 dynamic-datasource 实现逻辑
 * <p>
 * 1. 判断是否需要忽略租户
 * 2. 可能会存在方法嵌套调用的情况
 *
 * @author ycs
 */
public class TenantContextHolder {

    /**
     * 当前租户编号
     */
    private static final ThreadLocal<String> TENANT_ID = new TransmittableThreadLocal<>();

    /**
     * 是否忽略租户
     * <p>
     * 为什么要用链表存储(准确的是栈)
     * <pre>
     * 为了支持嵌套切换，如ABC三个service都是不同的数据源
     * 其中A的某个业务要调B的方法，B的方法需要调用C的方法。一级一级调用切换，形成了链。
     * 传统的只设置当前线程的方式不能满足此业务需求，必须使用栈，后进先出。
     * </pre>
     */
    private static final ThreadLocal<Deque<Boolean>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<Boolean>>("fanlinks-tenant") {
        @Override
        protected Deque<Boolean> initialValue() {
            return new ArrayDeque<>();
        }
    };

    /**
     * 获得当前线程租户配置
     */
    public static Boolean peek() {
        return LOOKUP_KEY_HOLDER.get().peek();
    }

    /**
     * 设置当前线程租户配置
     * <p>
     * 如非必要不要手动调用，调用后确保最终清除
     */
    public static Boolean push(Boolean ignore) {
        LOOKUP_KEY_HOLDER.get().push(ignore);
        return ignore;
    }

    /**
     * 清空当前线程租户配置
     */
    public static void poll() {
        Deque<Boolean> deque = LOOKUP_KEY_HOLDER.get();
        deque.poll();
        if (deque.isEmpty()) {
            LOOKUP_KEY_HOLDER.remove();
        }
    }

    /**
     * 强制清空本地线程
     */
    public static void clearTenantIgnore() {
        LOOKUP_KEY_HOLDER.remove();
    }

    /**
     * 当前是否忽略租户
     *
     * @return 是否忽略
     */
    public static boolean isIgnore() {
        return Boolean.TRUE.equals(peek());
    }


    /**
     * 获得租户ID
     */
    public static String getTenantId() {
        return TENANT_ID.get();
    }

    /**
     * 获得租户ID。如果不存在，则抛出异常
     */
    public static String getRequiredTenantId() {
        String tenantId = getTenantId();
        if (!StringUtils.hasLength(tenantId)) {
            throw new RuntimeException("租户ID不能为空");
        }
        return tenantId;
    }

    public static void setTenantId(String tenantId) {
        TENANT_ID.set(tenantId);
    }

    /**
     * 强制清空本地线程
     */
    public static void clearTenantId() {
        TENANT_ID.remove();
    }

}
